/*
 * u_buf.c
 *
 *  Created on: 2012-10-5
 *      Author: lin
 */
#include "zmalloc/zmalloc.h"

#include "u_buf.h"
#define U_BUF_VP_FRAME 512
u_buf_t *u_buf_new(uint32_t min, uint32_t max) {
    assert(max >= min && min > 0);
    char* str = zmalloc(sizeof(char) * min);
    u_buf_t *buf = zmalloc(sizeof(u_buf_t));
    buf->capacity = min;
    buf->max = max;
    buf->min = min;
    buf->len = 0;
    buf->cbuf = str;
    return buf;
}
int u_buf_extend(u_buf_t *a_buf, uint32_t extend) {
    assert(extend > 0);
    if (extend > a_buf->max) {
        return UBUF_OP_OVERFLOW;
    }
    if (a_buf->capacity >= extend || extend == a_buf->max) {
        return UBUF_OP_OK;
    }
    char* str = zmalloc(sizeof(char) * extend);
    memcpy(str, a_buf->cbuf, a_buf->len);
    zfree(a_buf->cbuf);
    a_buf->cbuf = str;
    return UBUF_OP_OK;
}
int u_buf_truncate(u_buf_t *a_buf, uint32_t trun) {
    assert(trun > 0 && trun >= a_buf->min && trun <= a_buf->max);
    if (a_buf->capacity == trun) {
        return UBUF_OP_OK;
    }
    char *str = zmalloc(sizeof(char) * trun);
    uint32_t len = a_buf->len;
    if (len <= trun) {
        memcpy(str, a_buf->cbuf, len);
    } else {
        memcpy(str, a_buf->cbuf, trun);
        a_buf->len = trun;
    }
    zfree(a_buf->cbuf);
    a_buf->cbuf = str;
    return UBUF_OP_OK;
}

int u_buf_append(u_buf_t *a_buf, const char *str, uint32_t strlen) {
    assert(strlen && str);
    uint32_t len = a_buf->len;
    uint32_t max = a_buf->max;
    uint32_t new_len = len + strlen;

    if (new_len > max) {
        return UBUF_OP_OVERFLOW;
    }

    if (new_len > a_buf->capacity) {
        uint32_t new_cap = a_buf->capacity * 2;
        if (new_cap > a_buf->max) {
            new_cap = a_buf->max;
        }
        u_buf_extend(a_buf, new_cap > new_len ? new_cap : new_len);
    }

    memcpy(a_buf->cbuf + len, str, strlen);
    a_buf->len = new_len;
    return UBUF_OP_OK;
}

int u_buf_adjust_append(u_buf_t * a_buf, const char *str, uint32_t strlen,
        int left_adjust, uint32_t in_width, uint32_t out_width, char space_char) {
    assert(strlen && str);
    uint32_t append_len = 0;
    uint32_t source_len = 0;
    uint32_t space_len = 0;

    /* calculate how many character will be got from str */
    if (out_width == 0 || str_len < out_width) {
        source_len = strlen;
    } else {
        source_len = out_width;
    }

    /* calculate how many character will be output */
    if (in_width == 0 || source_len >= in_width) {
        append_len = source_len;
        space_len = 0;
    } else {
        append_len = in_width;
        space_len = in_width - source_len;
    }

    /*  |-----append_len-----------| */
    /*  |-source_len---|-space_len-|  left_adjust */
    /*  |-space_len---|-source_len-|  right_adjust */
    uint32_t len = a_buf->len;
    uint32_t new_len = len + append_len;
    if (new_len > a_buf->capacity) {
        int rc = u_buf_extend(a_buf, new_len);
        if (rc != UBUF_OP_OK) {
            return rc;
        }
    }

    char *start = a_buf->cbuf + len;
    if (left_adjust) {
        memset(start + source_len, space_char, space_len);
        memcpy(start, str, source_len);
    } else {
        memset(start, space_char, space_len);
        memcpy(space_char + space_len, str, source_len);
    }
    a_buf->len = new_len;
    return UBUF_OP_OK;
}

int u_buf_vprintf(u_buf_t * a_buf, const char *format, va_list args) {
    uint32_t len = a_buf->len;
    uint32_t left = a_buf->capacity - len;
    int nwrite;
    if (U_BUF_VP_FRAME > left) {
        int extend_code = u_buf_extend(a_buf, a_buf->capacity + U_BUF_VP_FRAME);
        if (extend_code != UBUF_OP_OK) {
            return extend_code;
        }
        left = a_buf->capacity - len;
    }
    va_list ap;
    va_copy(ap, args);
    char *start = a_buf->cbuf + len;
    nwrite = vsnprintf(start, left, format, ap);
    if (nwrite < 0) {
        errors_no("vsnprintf fail", errno);
        return UBUF_OP_ERR;
    }
    if (nwrite <= left) {
        a_buf->len += nwrite;
        return UBUF_OP_OK;
    }
    int extend_code = u_buf_extend(a_buf, nwrite + len);
    if (extend_code != UBUF_OP_OK) {
        return extend_code;
    }
    left = a_buf->capacity - len;
    nwrite = vsnprintf(start, left, format, args);
    if (nwrite < 0) {
        errors_no("vsnprintf fail", errno);
        return UBUF_OP_ERR;
    }
    assert(nwrite <= left);
    a_buf->len += nwrite;
    return UBUF_OP_OK;
}

static inline int _printf_numstr(u_buf_t * a_buf, const unsigned char *p,
        uint32_t num_len, int width) {
    uint32_t zero_len,out_len;
    if (width > num_len) {
        zero_len = width - num_len;
        out_len = width;
    } else {
        zero_len = 0;
        out_len = num_len;
    }

    uint32_t new_len = a_buf->len + out_len;
    if (new_len > a_buf->capacity) {
        int rc = u_buf_extend(a_buf, new_len);
        if (rc != UBUF_OP_OK) {
            return rc;
        }
    }
    char *start = a_buf->cbuf + a_buf->len;
    memset(start, '0', zero_len);
    memcpy(start + zero_len, p, num_len);
    a_buf->len = new_len;
    return UBUF_OP_OK;
}
int u_buf_printf_dec64(u_buf_t * a_buf, uint64_t ui64, int width) {
    unsigned char tmp[STR_INT64_LEN + 1];

    unsigned char *p = tmp + STR_INT64_LEN;
    if (ui64 <= UINT32_MAX) {
        uint32_t ui32 = (uint32_t) ui64;
        do {
            *--p = (unsigned char) (ui32 % 10 + '0');
        } while (ui32 /= 10);

    } else {
        do {
            *--p = (unsigned char) (ui64 % 10 + '0');
        } while (ui64 /= 10);
    }

    /* zero or space padding */
    uint32_t num_len = (tmp + STR_INT64_LEN) - p;
    return _printf_numstr(a_buf,p,num_len,width);
}

int u_buf_printf_hex(u_buf_t * a_buf, uint32_t ui32, int width) {
    unsigned char tmp[STR_INT32_LEN + 1];
    uint32_t zero_len, out_len;
    static unsigned char hex[] = "0123456789abcdef";

    unsigned char *p = tmp + STR_INT32_LEN;
    do {
        /* the "(uint32_t)" cast disables the BCC's warning */
        *--p = hex[(uint32_t)(ui32 & 0xf)];
    } while (ui32 >>= 4);

    /* zero or space padding */
    uint32_t num_len = (tmp + STR_INT32_LEN) - p;
    return _printf_numstr(a_buf,p,num_len,width);
}
